/**
 *	@file		signal.h 
 *	@version	1.0a
 *	@author		rpvaras
 *
 *	This file is part of omcpp.
 *	
 *	
 *
 *	omcpp is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 *	(at your option) any later version.
 *
 *	omcpp is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with omcpp. If not, see <http://www.gnu.org/licenses/>.
 */
#ifndef __signal_h__
#define __signal_h__ 1

#include <omcpp/config.h>
#include <omcpp/delegate.h>
#include <map>

#ifdef OMCPP_SIGNAL_THREADSAFE
  #include <omcpp/mutex.h>
  #define OMCPP_SIGNAL_LOCK omcpp::auto_mutex lock(_signal_mtx);
#else
  #define OMCPP_SIGNAL_LOCK
#endif

namespace omcpp
{
	namespace _details
	{
		//--------------------------------------------------------
		//--------------------------------------------------------
		class signal_base
		{
		protected:
			signal_base();

		protected:
			unsigned long new_id();

		private:
			volatile unsigned long _next_id;

#ifdef OMCPP_SIGNAL_THREADSAFE
		protected:
			mutex _signal_mtx;
#endif
		};
	};

	//--------------------------------------------------------
	//--------------------------------------------------------
	template<typename _TyP1 = void, typename _TyP2 = void, typename _TyP3 = void>
	class signal : public _details::signal_base
	{
	public:
		typedef omcpp::ex_delegate<void, _TyP1, _TyP2, _TyP3> TyDelegate;

	public:
		TyDelegate& connect(unsigned long* cookie)
		{
			OMCPP_SIGNAL_LOCK;
			TyDelegate slot;
			unsigned long slot_id = new_id();
			TySlots::iterator iter = _slots.insert(TySlots::value_type(slot_id, slot)).first;
			*cookie = slot_id;
			return iter->second;
		}
		template< typename _TyThis, typename _TyX >
		unsigned long connect( _TyThis* lpThis, void(_TyX::*lpMethod)(_TyP1, _TyP2, _TyP3) )
		{
			OMCPP_SIGNAL_LOCK;
			TyDelegate slot;
			slot.bind<_TyThis, _TyX>(lpThis, lpMethod);
			unsigned slot_id = new_id();
			TySlots::iterator iter = _slots.insert(TySlots::value_type(slot_id, slot)).first;
			return slot_id;
		}
		void disconnect(unsigned long cookie)
		{
			OMCPP_SIGNAL_LOCK;
			TySlots::iterator iter = _slots.find(cookie);
			if( iter != _slots.end() )
			{
				_slots.erase(iter);
			}
		}
		void emit(_TyP1 p1, _TyP2 p2, _TyP3 p3)
		{
			OMCPP_SIGNAL_LOCK;
			TySlots::iterator iter;

			for( iter = _slots.begin(); iter != _slots.end(); iter ++ )
			{
				iter->second.invoke(p1, p2, p3);
			}
		}

	private:
		typedef std::map<unsigned long, TyDelegate> TySlots;
		TySlots _slots;
	};
	//--------------------------------------------------------
	template<typename _TyP1, typename _TyP2>
	class signal<_TyP1, _TyP2, void> : public _details::signal_base
	{
	public:
		typedef omcpp::ex_delegate<void, _TyP1, _TyP2> TyDelegate;

	public:
		TyDelegate& connect(unsigned long* cookie)
		{
			OMCPP_SIGNAL_LOCK;
			TyDelegate slot;
			unsigned long slot_id = new_id();
			TySlots::iterator iter = _slots.insert(TySlots::value_type(slot_id, slot)).first;
			*cookie = slot_id;
			return iter->second;
		}
		template< typename _TyThis, typename _TyX >
		unsigned long connect( _TyThis* lpThis, void(_TyX::*lpMethod)(_TyP1, _TyP2) )
		{
			OMCPP_SIGNAL_LOCK;
			TyDelegate slot;
			slot.bind<_TyThis, _TyX>(lpThis, lpMethod);
			unsigned slot_id = new_id();
			TySlots::iterator iter = _slots.insert(TySlots::value_type(slot_id, slot)).first;
			return slot_id;
		}
		void disconnect(unsigned long cookie)
		{
			OMCPP_SIGNAL_LOCK;
			TySlots::iterator iter = _slots.find(cookie);
			if( iter != _slots.end() )
			{
				_slots.erase(iter);
			}
		}
		void emit(_TyP1 p1, _TyP2 p2)
		{
			OMCPP_SIGNAL_LOCK;
			TySlots::iterator iter;

			for( iter = _slots.begin(); iter != _slots.end(); iter ++ )
			{
				iter->second.invoke(p1, p2);
			}
		}

	private:
		typedef std::map<unsigned long, TyDelegate> TySlots;
		TySlots _slots;
	};
	//--------------------------------------------------------
	template<typename _TyP1>
	class signal<_TyP1, void, void> : public _details::signal_base
	{
	public:
		typedef omcpp::ex_delegate<void, _TyP1> TyDelegate;

	public:
		TyDelegate& connect(unsigned long* cookie)
		{
			OMCPP_SIGNAL_LOCK;
			TyDelegate slot;
			unsigned long slot_id = new_id();
			TySlots::iterator iter = _slots.insert(TySlots::value_type(slot_id, slot)).first;
			*cookie = slot_id;
			return iter->second;
		}
		template< typename _TyThis, typename _TyX >
		unsigned long connect( _TyThis* lpThis, void(_TyX::*lpMethod)(_TyP1) )
		{
			OMCPP_SIGNAL_LOCK;
			TyDelegate slot;
			slot.bind<_TyThis, _TyX>(lpThis, lpMethod);
			unsigned slot_id = new_id();
			TySlots::iterator iter = _slots.insert(TySlots::value_type(slot_id, slot)).first;
			return slot_id;
		}
		void disconnect(unsigned long cookie)
		{
			OMCPP_SIGNAL_LOCK;
			TySlots::iterator iter = _slots.find(cookie);
			if( iter != _slots.end() )
			{
				_slots.erase(iter);
			}
		}
		void emit(_TyP1 p1)
		{
			OMCPP_SIGNAL_LOCK;
			TySlots::iterator iter;

			for( iter = _slots.begin(); iter != _slots.end(); iter ++ )
			{
				iter->second.invoke(p1);
			}
		}

	private:
		typedef std::map<unsigned long, TyDelegate> TySlots;
		TySlots _slots;
	};
	//--------------------------------------------------------
	template<>
	class signal<void, void, void> : public _details::signal_base
	{
	public:
		typedef omcpp::ex_delegate<void> TyDelegate;

	public:
		TyDelegate& connect(unsigned long* cookie)
		{
			OMCPP_SIGNAL_LOCK;
			TyDelegate slot;
			unsigned long slot_id = new_id();
			TySlots::iterator iter = _slots.insert(TySlots::value_type(slot_id, slot)).first;
			*cookie = slot_id;
			return iter->second;
		}
		template< typename _TyThis, typename _TyX >
		unsigned long connect( _TyThis* lpThis, void(_TyX::*lpMethod)() )
		{
			OMCPP_SIGNAL_LOCK;
			TyDelegate slot;
			slot.bind<_TyThis, _TyX>(lpThis, lpMethod);
			unsigned slot_id = new_id();
			TySlots::iterator iter = _slots.insert(TySlots::value_type(slot_id, slot)).first;
			return slot_id;
		}
		void disconnect(unsigned long cookie)
		{
			OMCPP_SIGNAL_LOCK;
			TySlots::iterator iter = _slots.find(cookie);
			if( iter != _slots.end() )
			{
				_slots.erase(iter);
			}
		}
		void emit()
		{
			OMCPP_SIGNAL_LOCK;
			TySlots::iterator iter;

			for( iter = _slots.begin(); iter != _slots.end(); iter ++ )
			{
				iter->second.invoke();
			}
		}

	private:
		typedef std::map<unsigned long, TyDelegate> TySlots;
		TySlots _slots;
	};
};

#endif //__signal_h__